//
// Created by buk on 8/28/24.
//
#include <future>
#include <fstream>
#include <regex>
#include <boost/filesystem.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#define BOOST_SPIRIT_THREADSAFE
#include <boost/signals2.hpp>

#include "spdlog/spdlog.h"
#include "../../../thirdparty/Simple-Web-Server/client_http.hpp"
#include "../../../thirdparty/Simple-Web-Server/server_http.hpp"

#include "web_server.h"
#include "../base/system_config.h"

#include <iostream>
#include <fstream>
#include <string>

using HttpServer = SimpleWeb::Server<SimpleWeb::HTTP>;
using namespace std;

static bool startsWithIgnoreCase(const std::string_view &a,
                                 const std::string_view &b)
{
    if (a.size() < b.size())
    {
        return false;
    }
    for (size_t i = 0; i < b.size(); i++)
    {
        if (::tolower(a[i]) != ::tolower(b[i]))
        {
            return false;
        }
    }
    return true;
}


std::string parseMultipartFormData(const std::string& data, std::string boundary, std::string web_path) {
    std::cout << "entering parseMultipartFormData" << std::endl;
    std::istringstream iss(data);
    std::string line;

    // 读取每个part
    while (std::getline(iss, line)) {
        if (line.find(boundary) != std::string::npos) {
            std::string name, fileName;

            std::getline(iss, line); // 读取Content-Disposition

          if (startsWithIgnoreCase(line, "content-disposition"))
          {
            static const std::string_view nameKey = "name=";
            static const std::string_view fileNameKey = "filename=";

            std::string valueView = line;

            // Extract name
            auto namePos = line.find(nameKey);
            if (namePos == std::string::npos)
            {
                // name absent
                return NULL;
            }
            namePos += nameKey.size();
            size_t nameEnd;
            if (line[namePos] == '"')
            {
                ++namePos;
                nameEnd = line.find('"', namePos);
            }
            else
            {
                nameEnd = line.find(';', namePos);
            }
            if (nameEnd == std::string::npos)
            {
                // name end not found
                return NULL;
            }
            name =
                valueView.substr(namePos, nameEnd - namePos);

            // Extract filename
            auto fileNamePos = line.find(fileNameKey, nameEnd);
            if (fileNamePos != std::string::npos)
            {
                fileNamePos += fileNameKey.size();
                size_t fileNameEnd;
                if (valueView[fileNamePos] == '"')
                {
                    ++fileNamePos;
                    fileNameEnd = valueView.find('"', fileNamePos);
                }
                else
                {
                    fileNameEnd = valueView.find(';', fileNamePos);
                }
                fileName =
                    valueView.substr(fileNamePos,
                                     fileNameEnd - fileNamePos);

            }
          }

          // std::string name = line.substr(line.find("name=\"") + 6);
          // name = name.substr(0, name.find("\""));
          spdlog::debug(line);

          // Content-Type: 行
          std::getline(iss, line);
          if (line.find("Content-Type") != std::string::npos)
          {
            spdlog::debug(line);
            // 跳过空行
            std::getline(iss, line);
          } else
          {
            // 跳过空行
          }
            // 读取数据
            std::string content;
            while (std::getline(iss, line)) {
                if (line.find(boundary) != std::string::npos) {
                    break; // 到达下一个part
                }
                content += line + "\n";
            }
            // 输出数据
            std::cout << "Name: " << name << std::endl;
            std::cout << "FileName: " << fileName << std::endl;
          // 如果是图片，则把content内容保存为图片文件
          // std::cout << "Content: " << content << std::endl;

          // std::string content = "这是要保存的文本内容";
          std::ofstream file(web_path + "/" + fileName); // 创建ofstream对象，打开文件

          if (file.is_open()) { // 检查文件是否成功打开
            file << content; // 将字符串写入文件
            file.close(); // 关闭文件
            std::cout << "文件保存成功" << std::endl;
          } else {
            std::cout << "文件保存失败" << std::endl;
          }

          return fileName;
        }
    }
}


namespace ai::facial_recognition {

WebServer::WebServer()
{
  this->server_port = 8080;
  this->init();
}
WebServer::WebServer(int port)
{
  this->server_port = port;
  this->init();
}
WebServer::~WebServer()
{
}


void WebServer::subscribeRequest(const RequestSignalType::slot_type &request_slot)
{
    request_signal_.connect(request_slot);
}

void WebServer::start()
{
  // 启动服务器，接收服务器实际监听的端口
  std::promise<unsigned short> server_port;
  this->jthread_server_ = std::jthread([this, &server_port]() {
    // Start server
    this->server.start([&server_port](unsigned short port) {
      server_port.set_value(port);
    });
  });

  spdlog::info("Web server is listening on port " + std::to_string(server_port.get_future().get()));
}

void WebServer::stop()
{
  this->server.stop();
  this->jthread_server_.request_stop();
  spdlog::info("Web server stopped");
}

void WebServer::init()
{
  this->server.config.port = this->server_port;

  SystemConfig system_config;
  this->web_root = system_config.readKey<std::string>("global","web_root","wwwroot");

  server.resource["^/detect_face$"]["POST"] = [this](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
      thread work_thread([this,response, request] {
          auto msg = std::make_shared<RequestMessage>();
          std::string fileName;
          std::string content_type_;
          std::string boundary_, dashBoundaryCrlf_, crlfDashBoundary_;
          static const std::string_view multipart = "multipart/form-data";
          static const std::string_view boundaryEq = "boundary=";
          const std::string dash_ = "--";
          const std::string crlf_ = "\r\n";

          for(auto it=request->header.begin(); it != request->header.end(); ++it) {
              std::string temp = it->first;
              spdlog::debug(temp + " : " + it->second);
              if (temp == "Content-Type") {
                  auto contentType = it->second;
                  if (!startsWithIgnoreCase(contentType, multipart))
                  {
                      return;
                  }
                  auto pos = contentType.find(boundaryEq, multipart.size());
                  if (pos == std::string::npos)
                  {
                      return;
                  }

                  pos += boundaryEq.size();
                  size_t pos2;
                  if (contentType[pos] == '"')
                  {
                      ++pos;
                      pos2 = contentType.find('"', pos);
                  }
                  else
                  {
                      pos2 = contentType.find(';', pos);
                  }
                  if (pos2 == std::string::npos)
                      pos2 = contentType.size();

                  boundary_ = contentType.substr(pos, pos2 - pos);

                  fileName = parseMultipartFormData(request->content.string(), boundary_, this->web_root);
              }
          }

          spdlog::debug(request->content.size());
          spdlog::debug("filename: " + fileName);
          msg->image_file_name = fileName;
          msg->web_root = this->web_root;

          // 上传图片成功，开始自动识别
          this->request_signal_(msg);

          response->write("Work(detect_face) done");
      });

      work_thread.detach();
  };

  // Add resources using path-regex and method-string, and an anonymous function
  // POST-example for the path /string, responds the posted string
  server.resource["^/string$"]["POST"] = [this](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
    auto content = request->content.string();
    auto msg = std::make_shared<BaseMessage>();
    msg->context.put("content", content);
    this->writeResponse(response, msg);
  };

  server.resource["^/json$"]["POST"] = [this](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> request) {
    try {
      boost::property_tree::ptree pt;
      read_json(request->content, pt);

      auto msg = std::make_shared<BaseMessage>();
      msg->context.put("firstName", pt.get<string>("firstName"));
      msg->context.put("lastName", pt.get<string>("lastName"));
      this->writeResponse(response, msg);
    }
    catch(const exception &e) {
      *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n"
                << e.what();
    }
  };

  // GET-example for the path /info
  // Responds with request-information
  server.resource["^/info$"]["GET"] = [](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> request) {
    stringstream stream;
    stream << "<h1>Request from " << request->remote_endpoint().address().to_string() << ":" << request->remote_endpoint().port() << "</h1>";

    stream << request->method << " " << request->path << " HTTP/" << request->http_version;

    stream << "<h2>Query Fields</h2>";
    auto query_fields = request->parse_query_string();
    for(auto &field : query_fields)
      stream << field.first << ": " << field.second << "<br>";

    stream << "<h2>Header Fields</h2>";
    for(auto &field : request->header)
      stream << field.first << ": " << field.second << "<br>";

    response->write(stream);
  };

  // GET-example for the path /match/[number], responds with the matched string in path (number)
  // For instance a request GET /match/123 will receive: 123
  server.resource["^/match/([0-9]+)$"]["GET"] = [this](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> request) {
    auto msg = std::make_shared<BaseMessage>();

    // Set the response content
    msg->context.put("content", request->path_match[1].str());
    this->writeResponse(response, msg);
  };

  // GET-example simulating heavy work in a separate thread
  server.resource["^/work$"]["GET"] = [](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> /*request*/) {
    thread work_thread([response] {
      this_thread::sleep_for(chrono::seconds(5));
      response->write("Work done");
    });
    work_thread.detach();
  };

  // Default GET-example. If no other matches, this anonymous function will be called.
  // Will respond with content in the web/-directory, and its subdirectories.
  // Default file: index.html
  // Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server
  server.default_resource["GET"] = [this](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> request) {
    try {
      auto web_root_path = boost::filesystem::canonical(this->web_root);
        std::cout << web_root_path << std::endl;
      auto path = boost::filesystem::canonical(web_root_path / request->path);
        std::cout << request->path << std::endl;
      // Check if path is within web_root_path
      if(distance(web_root_path.begin(), web_root_path.end()) > distance(path.begin(), path.end()) ||
         !equal(web_root_path.begin(), web_root_path.end(), path.begin()))
        throw invalid_argument("path must be within root path");
      if(boost::filesystem::is_directory(path))
        path /= "index.html";

      SimpleWeb::CaseInsensitiveMultimap header;

      // Uncomment the following line to enable Cache-Control
      // header.emplace("Cache-Control", "max-age=86400");

      auto ifs = make_shared<ifstream>();
      ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);

      if(*ifs) {
        auto length = ifs->tellg();
        ifs->seekg(0, ios::beg);

        header.emplace("Content-Length", to_string(length));
        response->write(header);

        // Trick to define a recursive function within this scope (for example purposes)
        class FileServer {
        public:
          static void read_and_send(const shared_ptr<HttpServer::Response> &response, const shared_ptr<ifstream> &ifs) {
            // Read and send 128 KB at a time
            static vector<char> buffer(131072); // Safe when server is running on one thread
            streamsize read_length;
            if((read_length = ifs->read(&buffer[0], static_cast<streamsize>(buffer.size())).gcount()) > 0) {
              response->write(&buffer[0], read_length);
              if(read_length == static_cast<streamsize>(buffer.size())) {
                response->send([response, ifs](const SimpleWeb::error_code &ec) {
                  if(!ec)
                    read_and_send(response, ifs);
                  else
                    cerr << "Connection interrupted" << endl;
                });
              }
            }
          }
        };
        FileServer::read_and_send(response, ifs);
      }
      else
        throw invalid_argument("could not read file");
    }
    catch(const exception &e) {
      response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path " + request->path + ": " + e.what());
    }
  };

  server.on_error = [](shared_ptr<HttpServer::Request> /*request*/, const SimpleWeb::error_code & /*ec*/) {
    // Handle errors here
    // Note that connection timeouts will also call this handle with ec set to SimpleWeb::errc::operation_canceled
  };
}

void WebServer::writeResponse(std::shared_ptr<HttpServer::Response> response, std::shared_ptr<BaseMessage> msg) {
  auto node1 = msg->context.get_optional<int>("errcode");
  if (!node1.is_initialized()) {
    msg->context.put<int>("errcode", 0);
  }

  auto node2 = msg->context.get_optional<int>("errmsg");
  if (!node2.is_initialized()) {
    msg->context.put("errmsg", "");
  }

  std::ostringstream  os;
  write_json(os, msg->context);

  response->write(os.str());
}


}